Add Personal Timesheet View to Me Tab on Dashboard - #469
Conversation
Step 1 of the redesign plan. Bottom nav tabs are now Dashboard, Huddle, Clock (center FAB), Tickets, Teams. Settings is still reachable via the avatar dropdown in the header, matching the prototype's priority order.
Step 2 of the redesign plan. Adds a CSS-only override that turns the @mieweb/ui Modal into a bottom-sheet on mobile: slides up from the bottom, full width, rounded top corners only, capped at 88dvh with internal scroll. Applies to every consumer of the shared Modal (tickets, teams, org, invite, confirm-delete, feedback, media, etc.) without forking the component. Desktop behavior is unchanged.
Step 3. Huddle now uses AppPage flush + fill so PostCards stretch to the viewport edges on mobile (no side gutter, no card radius). Tabs, composer, drafts, and search input keep px-4 mobile gutter so they still look inset. Desktop keeps the max-w-4xl reading column (Phase 2 will tighten this to max-w-2xl). No changes to composer logic or post rendering.
The quick-stats grid in DashboardPage.tsx already uses 'grid grid-cols-2 gap-3', which satisfies the mobile 2-column requirement from the redesign plan. Me/Team scope is controlled by the existing OrgTeamSwitcher / TeamContext, not a separate toggle inside the page, so no layout change is needed for step 4. Numbers populate correctly for both personal and team scopes.
Step 5. ClockPage was already a single-column stack (banner status -> plan/wrap-up composer -> primary action -> punch-clock display). On mobile the primary action row (Post plan and clock in / Post wrap-up and clock out / Clock in / Clock out / Break) now stacks vertically with each Button spanning w-full so the tap target reaches the full column width. Desktop keeps the horizontal flex-wrap row via sm:flex-row + sm:w-auto. Plan-first gate logic in useClockToggle is untouched -- all 11 tests in useClockToggle.test.ts still pass.
Replaces the anchored Dropdown popover with the shared Modal primitive: - Mobile: full-width bottom sheet (existing Modal CSS from step 2) - Desktop: centered dialog (existing Modal CSS from step 10) - Organization select + Team list with member counts + '+ New team', matching design/redesign-prototype/index.html's OrgTeamSwitcher exactly - Portals the Modal to document.body so it escapes AppHeader's backdrop-blur containing block (backdrop-filter creates a new containing block for position:fixed descendants, which was pinning the sheet near the header instead of the viewport) - Preserves existing features not in the prototype: org role labels, pending join-request badges, awaiting-approval section
- Header trigger now matches the prototype's stacked two-line layout: brand mark + bold team name on top, muted 'Org name ▾' below — replacing the single-line 'Org > Team' pill. - Replaced the large translateY(100%) slide-up keyframe with the prototype Modal's actual animation: a quiet fade + 4px settle (150ms ease-out). The prototype never really 'slides' a large distance — it docks the sheet to the bottom and just fades/settles it in, which is what read as clean instead of a 'jump'.
- AppPage gains an optional titleActions slot so pages can render controls on the same row as the page title (right-aligned), used here for the Me/Team segmented toggle from the redesign prototype. - DashboardPage: 'Me' (default) shows the signed-in user's own hours today, open/closed/high-priority tickets, and running timers; 'Team' shows the existing team-wide aggregates unchanged. - The toggle is always visible now (previously personal-team users had the team members / time-logged sections silently hidden via isPersonalTeam) — switching to Team on a personal team just shows a team of one instead of hiding the section.
The prototype's desktop sidebar renders the Organization select + Team list + '+ New team' inline, always visible — no header button/modal. Extracted that content into OrgTeamSwitcherPanel (shared with the mobile bottom sheet) and render it directly in Sidebar.tsx's brand block for the expanded desktop rail. The header pill + Modal in OrgTeamSwitcher is now md:hidden (mobile-only) since desktop no longer needs it — mobile behavior is unchanged.
…prototype" This reverts commit b7923fd.
… switcher - Teams page: replace tabs with always-visible Members/Pending sections - Add horizontal-scroll team pill switcher (Personal + selected team lead, rest follow) - Consolidate Rename/Delete/Copy/Share into Team Settings modal; keep code+copy under team name - Restore per-member 3-dot menu (Make/Remove Admin, Set Password, Remove Member) for all rows, including self (minus self-demote/remove) - Compact icon-only Create/Join Team buttons in page title actions - Move admin Timesheet view from Teams to Dashboard's Team tab with Overview/Timesheet toggle and deep-link support - Scope the mobile bottom-sheet Modal treatment to only the org/team switcher modal, restoring default centered dialogs everywhere else - Update e2e tests and notification deep links to match
Give the plan/wrap-up composer the same Photo/Video/Doc/Pulse/Ticket/ @Mention bar as the Huddle composer (extracted into shared ComposerAttachButtons/ComposerChips so both stay in sync), and match the redesign prototype's status card (bold elapsed-session timer instead of the wall clock) and Recent sessions list.
- Override @mieweb/ui's mobile full-screen defaults (min-h-dvh, rounded-none) so all modals render as centered popups on every screen size - Add horizontal margin on small screens so modals don't touch viewport edges - Move org-switcher bottom-sheet rules outside the 767px media query so the bottom-sheet treatment applies on all screen sizes (not just mobile)
Drop the avatar circle and nested flex wrapper from the expanded composer, trim its padding, and widen the feed's max-width from 2xl to 4xl so it isn't stranded in a narrow strip on wide dashboards.
Replace text "Copy" link with a tight code badge + copy + share icon button group so the invite code row stays uncluttered. Share uses the Web Share API with a clipboard fallback for desktop.
Match Clock's column width to Huddle's (max-w-2xl -> max-w-4xl). Also drop the opacity/saturate dimming on the bottom-nav Clock In/Out FAB when a plan or wrap-up is pending — it's a plain link to the clock page, not a disabled control, so it shouldn't look blocked.
The per-row "..." menu used the plain library Dropdown, which positions its menu absolutely inside the ticket list's overflow-y-scroll container. Per the CSS overflow spec, once one axis is scrollable the other axis clips too, so the menu was silently cut off for rows near the bottom of the list -- the same class of bug FilterDropdown already works around elsewhere in this file. Portal the menu to document.body with fixed coordinates computed from the trigger's rect, same pattern.
- Add meView state ('overview' | 'timesheet') to the Me tab
- Add Overview / Timesheet secondary toggle under Me tab
- Render AdminTimesheetPanel pre-loaded with the current user when meView is 'timesheet'
- Add userToTeamMember helper to convert TimecoreUser to TeamMember shape
- AppHeader: hide the clock-page shortcut on /app/clock so its 'Clock in' aria-label doesn't collide with the ClockPage's own Clock In button (Playwright strict-mode fail).
- ClockPage e2e page object: .clock-banner → .clock-status; status text 'On shift' → 'Clocked in'; plan gate text 'Write a plan before starting this session' → 'Plan before you clock in' — the underlying DOM was renamed during the redesign.
- Team switcher tests (team-qr-share, team-auto-accept, org-invite-by-email, timesheet-calc): getByRole('menuitem', {teamName}) → getByRole('dialog').getByRole('button', {teamName}); the switcher renders a modal of plain buttons now, not a menu.
- team-qr-share: 'Share team QR code' button lives inside Team Settings; the main teams page opens the share modal via 'Share team invite link'.
- teams: 'Copy' button is now 'Copy team code'; disambiguate the created team header by asserting the h3 CardTitle instead of unscoped getByText.
- dashboard: scope sidebar nav-item assertions to the 'Main navigation' nav, since the dashboard body now has its own Timesheet/Team tabs.
- timesheet-calc admin view: scope the 'Timesheet' tab click to <main> to skip the sidebar's identically-named nav button.
- playwright.config: bump retries 1→2 and per-test timeout 30s→45s; grant clipboard-read/write permission so copy-link flows can actually write to the clipboard (previously NotAllowedError).
- loginAs: after waitForURL('**/dashboard') also await waitForLoadState('networkidle') so the post-login session/orgs/teams fanout is settled before the test navigates — otherwise the next page.goto() occasionally races the session-cookie handshake and bounces back to the login screen.
- realtime specs (media-library, messages, notifications, team-members, timesheet): give the login toHaveURL check an explicit 15s timeout — the default 5s occasionally clips when the backend is warming up under sequential load.
- notifications:52, channels:140, pulsevault:348, notifications:28: test.setTimeout(60000) — these are the four historically flaky tests; 45s occasionally isn't enough late in the suite.
- profile-routing selectTestTeam: after page.reload(), wait for networkidle first, then require the Teams heading with a 20s timeout — the reload path is where the auth cookie sometimes hadn't propagated, dropping us on the login screen.
jlocala1
left a comment
There was a problem hiding this comment.
Went through the whole thing and clicked around the preview deploy. The redesign looks good and the commit list in the description made this way easier to follow than a 48 commit PR has any right to be. Couple of things I ran into.
The big one is the notification URLs, left that inline on notify-core.js. Short version is the old links already out there stop working and fail silently. One thing I couldn't check is how many old notifications are actually sitting around, so if those get cleaned up regularly this might be smaller than it looks.
Second thing: there are two personal timesheets now and they don't match. /app/timesheet is still routed and still linked from the sidebar, the More sheet and the command palette. I compared them on the preview with the same account. The old page has a Break Hours card and an Add Entry button. The new Me > Timesheet has neither, and it also shows a Member dropdown where the only option is yourself, since it's reusing AdminTimesheetPanel with a one element member list. So someone who finds the dashboard one first can't log a manual entry and doesn't see their break hours. Which one is meant to be the real one going forward? If it's the dashboard, the panel probably wants a way to hide the member select and the missing pieces ported over.
Also worth knowing, and not your fault: Playwright Checks and Backend Checks are both if: false in checks.yml, so the e2e changes in here and the retries and timeout bumps in playwright.config.ts never actually ran. The green checks are only lint, typecheck and build. Did you get a manual pass over the suite locally?
Two minor ones I didn't bother with inline. The styles.css modal overrides reach into @mieweb/ui internals (data-slot, data-state) with !important across every modal in the app, so a library bump could break it with nothing failing to catch it, might be worth a Modal variant upstream instead. And the More sheet in BottomNav has aria-modal="true" but focus stays on the page behind it when it opens, so keyboard users tab in from the top. If I'm already gone when these are sorted, feel free to dismiss this rather than waiting on me.
Removes the decorative ClockStrand from the desktop header middle section. The header now shows only the timer pill and action buttons when clocked in.
…e ambient background CSS Restores the original translateX-only keyframe. Removes all ambient-bg orb rules that were added for the background color experiment.
Removes the app-bg class and initAmbientBg hook call from AppLayout. Removes AmbientBgRow toggle from Settings Appearance section and deletes useAmbientBg.ts.
Removes the ClockStrand from the timer pill so mobile shows only the HH:MM:SS digits. Also removes the now-unused ClockStrand import.
Reduces gap, card padding, and timer font size on small screens. Hides the decorative wave strand on mobile. Shrinks the composer heading on mobile. All layout values restore to their original sizes at md breakpoint.
After updateSettings saves requirePlanForClock: true, fan-out a notification to every team member (except the admin who made the change) via createNotification, which persists to the inbox and fires a push.
Initialises teamFilter from selectedTeamId so the list opens pre-filtered to the active team. A useEffect also follows the team switcher so changing teams in the header updates the filter automatically.
…reak, useWorkspaceGreeting
…BottomNav focus trap, styles.css comment - TeamsPage: forward old /app/teams?tab=timesheet notification URLs to /app/dashboard - TeamContext: expose allTeams (unscoped) alongside scoped teams - DashboardPage: switch org when deep-link teamId belongs to a different org - BottomNav More sheet: move focus in on open, trap Tab/Shift+Tab, restore focus to trigger on close - styles.css: document data-slot/data-state internals dependency with TODO to fix upstream
…k active tickets - Remove Me/Team scoping from the Dashboard entirely - Single Overview/Timesheet toggle in the content area (no more two-toggle confusion) - Timesheet tab shows AdminTimesheetPanel for admins, PersonalTimesheetPanel for others - Stats cards always show team data - Active ticket rows are now clickable buttons that navigate to /app/tickets/:id
…ew/Timesheet - Me/Team pill toggle back in the title bar - Stats cards (Hours, Open tickets, Closed today, High priority) scope to Me or Team - Team Members and Time logged today sections only appear on Team tab - Single Overview/Timesheet toggle in content (no duplicate toggles) - Active ticket rows remain clickable, navigating to /app/tickets/:id
Oh. I see it. Are there not test roles for auto logging in? Every test environment should have a drop-down role that we can just login automatically with a single click |
jlocala1
left a comment
There was a problem hiding this comment.
Re-checked all three and they're properly fixed, not papered over. The TeamsPage forward keeps memberId and teamId, the allTeams fallback handles the cross-org case, and consolidating onto one PersonalTimesheetPanel is a better outcome than what I asked for.
One new thing came out of the consolidation, left inline with a screenshot. Not a blocker in my view. Approving so this isn't stuck waiting on me, today is my last day.
| ↑ {membersClocked.length} active | ||
| {/* ── Timesheet view: admin panel for admins, personal panel for everyone else ── */} | ||
| {view === 'timesheet' && | ||
| (canViewTimesheet && selectedTeamId ? ( |
There was a problem hiding this comment.
All three fixes look great! One more thing kicked up. This picks the panel off canViewTimesheet alone, which is isAdmin && !isPersonalWorkspace, so it never looks at the Me/Team tab. Checked it on the preview as an admin of a non-personal team, screenshot attached: Me tab is selected, the banner says "Showing your own stats", and it's still rendering AdminTimesheetPanel with a member picker. That panel has no Add Entry and no Break Hours. /app/timesheet redirects into the same view and the Timesheet nav entry is gone from the sidebar, bottom nav and command palette, so an admin can't log a manual past session or see their own break hours unless they switch to their Personal workspace, which does show the personal panel with both. On main an admin got the personal timesheet from the sidebar like anyone else. Would gating on tab === 'me' as well do it?
There was a problem hiding this comment.
Fixed. The Me tab now always renders the personal timesheet.
The bug — DashboardPage.tsx:393 chose the panel from canViewTimesheet alone (isAdmin && !isPersonalWorkspace), which has nothing to do with the Me/Team tab. So for any admin, both tabs rendered AdminTimesheetPanel — the team timesheet with the member picker and no Add Entry.
Added showAdminTimesheet = canViewTimesheet && tab === 'team' (:102) and used it to select the panel. Me → PersonalTimesheetPanel (own entries, Add Entry, per-session edit/delete, no member picker); Team → AdminTimesheetPanel for admins.
Scoped the teamApi.getMembers fetch to the same condition (:143-160) so the Me tab no longer fetches the member list it doesn't use.
… Team The Timesheet view picked its panel from canViewTimesheet alone, which only means "admin on a non-personal workspace" and says nothing about the active tab. Admins therefore got AdminTimesheetPanel — member picker, no Add Entry — in both Me and Team. Gate the admin panel on tab === 'team' so Me → Timesheet is always the signed-in user's own timesheet, and scope the getMembers fetch to the same condition so the Me tab stops fetching a member list it doesn't render. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Will address the timezone boundary bug in a separate PR.
|
I will fix the deployment pr preview having test roles to test it easily without having to create a new account in a different PR @horner |
Overview
This branch implements the full mobile/desktop UI redesign for TimeHuddle — bringing the app to parity with the redesign prototype across layout, navigation, and interaction patterns — plus several quality-of-life fixes and feature additions on top.
Changes by Commit
Navigation & Shell
OrgTeamSwitcher
Dashboard
Clock Page
Huddle
Tickets
Teams Page
Modals & Toasts
New Features
AdminTimesheetPanelDocs
design/Acceptance Criteria
npm run typecheckandnpm run lintpass clean